<?php
/**
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; version 3 of the License.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
*/

/**
 * Crypt with auto salt
 * @package Util
 * @version 0.2
 */
namespace Util;

class Bcrypt {

	private $_rounds;
	private $_randomState;

	/**
	 * Constructor of Bcyrpt
	 * @param int $rounds 
	 */
	public function __construct($rounds = null) {
		if (is_null($rounds)){
			$rounds = 12;
		}
		if (CRYPT_BLOWFISH != 1) {
			throw new Exception("bcrypt not supported in this installation. See http://php.net/crypt");
		}

		$this->_rounds = $rounds;
	}

	/**
	 * Factory method for Bcrypt
	 * @param type $rounds
	 * @return \Util\Bcrypt
	 */
	public static function init($rounds = null) {
		return new self($rounds);
	}

	/**
	 * Creates hash from $input
	 * @param string $input
	 * @return bool|string
	 */
	public function hash($input) {
		$hash = crypt($input, $this->_getSalt());

		if (strlen($hash) > 13)
			return $hash;

		return false;
	}

	/**
	 * Verifies if hashed $input is equal to $existingHash
	 * @param string $input
	 * @param string $existingHash
	 * @return bool
	 */
	public function verify($input, $existingHash) {
		return crypt($input, $existingHash) === $existingHash;
	}

	/**
	 * Creates salt for one way crypting
	 * @return string
	 */
	private function _getSalt() {
		return sprintf('$2a$%02d$', $this->_rounds) . $this->_encodeBytes($this->_getRandomBytes(16));
	}

	/**
	 * Generates random bytes
	 * @param int $count
	 * @return string
	 */
	private function _getRandomBytes($count) {
		$bytes = '';

		if (function_exists('openssl_random_pseudo_bytes') && (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL slow on Win
			$bytes = openssl_random_pseudo_bytes($count);
		}

		if ($bytes === '' && is_readable('/dev/urandom') && ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) {
			$bytes = fread($hRand, $count);
			fclose($hRand);
		}

		if (strlen($bytes) < $count) {
			$bytes = '';

			if ($this->_randomState === null) {
				$this->_randomState = microtime();
				if (function_exists('getmypid')) {
					$this->_randomState .= getmypid();
				}
			}

			for ($i = 0; $i < $count; $i += 16) {
				$this->_randomState = md5(microtime() . $this->_randomState);

				if (PHP_VERSION >= '5') {
					$bytes .= md5($this->_randomState, true);
				} else {
					$bytes .= pack('H*', md5($this->_randomState));
				}
			}

			$bytes = substr($bytes, 0, $count);
		}

		return $bytes;
	}

	/**
	 * Create byte code from string
	 * @param string $input
	 * @return string 
	 */
	private function _encodeBytes($input) {
		// The following is code from the PHP Password Hashing Framework
		$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

		$output = '';
		$i = 0;
		do {
			$c1 = ord($input[$i++]);
			$output .= $itoa64[$c1 >> 2];
			$c1 = ($c1 & 0x03) << 4;
			if ($i >= 16) {
				$output .= $itoa64[$c1];
				break;
			}

			$c2 = ord($input[$i++]);
			$c1 |= $c2 >> 4;
			$output .= $itoa64[$c1];
			$c1 = ($c2 & 0x0f) << 2;

			$c2 = ord($input[$i++]);
			$c1 |= $c2 >> 6;
			$output .= $itoa64[$c1];
			$output .= $itoa64[$c2 & 0x3f];
		} while (1);

		return $output;
	}

}

?>